/*Copyright 2010 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/
var jsonrpc_client = function(dataSender) {
    var callbacks = [];
    var nextCallId = 1;
    
	function getNextCallId() {
		var callId = nextCallId;
		nextCallId = nextCallId + 1;
		return callId;
	}
	
	function isArray(obj) {
        return obj.constructor.toString().indexOf("Array") !== -1;
    }

    function processMessage(message, meta, responder) {
        if (!message.jsonrpc || message.jsonrpc !== "2.0") { throw "invalid rpc reply"; }
        if (typeof message.result !== "undefined" && typeof message.error !== "undefined") { throw "invalid rpc reply"; }
        if (typeof message.id === "undefined") { throw "invalid rpc reply"; }
        var cbobj = callbacks[message.id];
        if (!cbobj) { throw "unmatched rpc reply"; }
		if (!cbobj.subscribe) {
			delete callbacks[message.id];
		}
        if (typeof message.result !== "undefined" && typeof message.error !== "undefined") { throw "error and result received; invalid rpc reply"; }
        if (typeof message.result !== "undefined") {
            if (typeof cbobj.success === "function") {
                cbobj.success(message.result);
            }
        } else if (typeof message.error !== "undefined") {
            if (typeof cbobj.error === "function") {
                cbobj.error(message.error);
            }
        }
    }
    
    var jsonrpc_obj = {};
    jsonrpc_obj.call = function(methodName, parameters, success, error, subscribe) {
		if (typeof methodName !== "string") { throw "TypeError: methodName is not a valid string."; }
		if (!parameters || !isArray(parameters)) { throw "TypeError: parameters is not a valid array."; }
		if (typeof subscribe === "undefined") { subscribe = false; }
		
        var callId = getNextCallId();
		
        var callObj = {
            jsonrpc: "2.0",
            method: methodName,
            params: parameters,
            id: callId
        };
        callbacks[callId] = {
			subscribe: subscribe,
            success: success,
            error: error
        };
        dataSender(callObj);
    };

	jsonrpc_obj.subscribe = function(methodName, parameters, success, error) {
		return this.call(methodName, parameters, success, error, true);
	};
    
    jsonrpc_obj.addMessageReceiver = function(provider) {
        provider(processMessage);
    };
    return jsonrpc_obj;
};

var getPostMessageSender = function(win, origin) {
    return function(message) {
        win.postMessage(JSON.stringify(message), origin);
    };
};

/*global window*/

function getPostMessageReceiver(origin, win) {
    return function (messageCallback) {
        var data;
        if (typeof JSON === "undefined" || typeof JSON.stringify === "undefined" || typeof JSON.parse === "undefined") {
            throw "jsonrpc requires the JSON library";
        }
		
        var messageHandler = function(e) {
			if (typeof win !== "undefined" && win !== e.source) {
				return;
			}
            if (typeof origin !== "undefined" && origin !== "*" && origin !== e.origin) {
                return;
            }
			
            try {
                data = JSON.parse(e.data);
            } catch(error) {
                throw "error parsing json";
            }
            messageCallback(data, {origin: e.origin, source: e.source}, function(response) {
                e.source.postMessage(JSON.stringify(response), e.origin);
            });
        };
		if (window.addEventListener) {
			window.addEventListener("message", messageHandler, false);
		} else if (window.attachEvent) {
			window.attachEvent("onmessage", messageHandler);
		}
    };
}